/**
 *	TTS_Engine
 *			TTS Engine to Convert the text to Speech.
 *	@Author : Amit Kumar
 *	@Date :
 *
 */

#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <math.h>
#include <string.h>
#include <jni.h>
#include "HTS_engine.h"

#include <android/log.h>

#define LOG_TAG   "libAMIT_HTS_ENGINE"
#define LOGI(ARG)  __android_log_write(ANDROID_LOG_INFO, LOG_TAG, ARG)
#define LOGE(ARG)  __android_log_write(ANDROID_LOG_ERROR,LOG_TAG, ARG)

#define INPUT_BUFF_SIZE 2048
/* Global Variable  */
/* number of speakers for interpolation */

double *rate_interp = NULL; // Amit Note : Check For this variable

/* file names of models */
char **fn_ms_lf0;
char **fn_ms_mcp;
char **fn_ms_dur;
char **fn_ms_str;
/* number of each models for interpolation */
int num_ms_lf0 = 0, num_ms_mcp = 0, num_ms_dur = 0, num_ms_str = 0;

/* file names of trees */
char **fn_ts_lf0;
char **fn_ts_mcp;
char **fn_ts_dur;
char **fn_ts_str;
/* number of each trees for interpolation */
int num_ts_lf0 = 0, num_ts_mcp = 0, num_ts_dur = 0, num_ts_str = 0;

/* file names of windows */
char **fn_ws_lf0 = NULL;
char **fn_ws_mcp;
char **fn_ws_str;
int num_ws_lf0 = 0, num_ws_mcp = 0, num_ws_str = 0;

/* file names of global variance */
char **fn_ms_gvl = NULL;
char **fn_ms_gvm = NULL;
char **fn_ms_gvs = NULL;
int num_ms_gvl = 0, num_ms_gvm = 0, num_ms_gvs = 0;

/* file names of global variance trees */
char **fn_ts_gvl = NULL;
char **fn_ts_gvm = NULL;
char **fn_ts_gvs = NULL;
int num_ts_gvl = 0, num_ts_gvm = 0, num_ts_gvs = 0;

/* file name of global variance switch */
char *fn_gv_switch = NULL;

/* global parameter */
int sampling_rate = 16000;
int fperiod = 80;
double alpha = 0.42;
double stage = 0.0; /* gamma = -1.0/stage */
double beta = 0.0;
int audio_buff_size = 1600;
double uv_threshold = 0.5;
double gv_weight_lf0 = 0.7;
double gv_weight_mcp = 1.0;
double gv_weight_str = 1.0;

/* engine */
HTS_Engine engine;

/**
 * Init(char *path): This Function Initialise  The TTS Engine.
 * 		It take One Time Parameter.
 * 		Path of the Language Dir.
 *
 * 	Returns: Its return the state or result of the function
 * @PARAMS: path
 * @Auhtor: Amit Kumar
 * @Date  :
 *
 */

int Init(char *path) {
	int i;
	int num_interp = 0;
	HTS_Boolean use_log_gain = FALSE;

	/* initialize (stream[0] = spectrum , stream[1] = lf0) */
	HTS_Engine_initialize(&engine, 3);
	/* prepare for interpolation */
	num_interp = 1; /*GetNumInterp(argc, argv);*/
	rate_interp = (double *) calloc(num_interp, sizeof(double));
	for (i = 0; i < num_interp; i++)
		rate_interp[i] = 1.0;

	prepareLanguageParameter(path, num_interp); /*Function created By Amit Kumar*/

	/* number of models,trees check */
	if (num_interp != num_ts_lf0 || num_interp != num_ts_mcp
			|| num_interp != num_ts_dur || num_interp != num_ms_lf0
			|| num_interp != num_ms_mcp || num_interp != num_ms_dur) {
		{
			Error(
					1,
					"hts_engine: specify %d models(trees) for each parameter.\n",
					num_interp);
			//TODO: Error Handling in INIT fucntions
			return 0;
		}
	}
	/* load duration model */
	HTS_Engine_load_duration_from_fn(&engine, fn_ms_dur, fn_ts_dur, num_interp);
	/* load stream[0] (spectrum model) */
	HTS_Engine_load_parameter_from_fn(&engine, fn_ms_mcp, fn_ts_mcp, fn_ws_mcp,
			0, FALSE, num_ws_mcp, num_interp);
	/* load stream[1] (lf0 model) */
	HTS_Engine_load_parameter_from_fn(&engine, fn_ms_lf0, fn_ts_lf0, fn_ws_lf0,
			1, TRUE, num_ws_lf0, num_interp);
	HTS_Engine_load_parameter_from_fn(&engine, fn_ms_str, fn_ts_str, fn_ws_str,
			2, FALSE, num_ws_str, num_interp);

	/* load gv[0] (GV for spectrum) */
	if (num_interp == num_ms_gvm) {
		if (num_ms_gvm == num_ts_gvm)
			HTS_Engine_load_gv_from_fn(&engine, fn_ms_gvm, fn_ts_gvm, 0,
					num_interp);
		else
			HTS_Engine_load_gv_from_fn(&engine, fn_ms_gvm, NULL, 0, num_interp);
	}
	/* load gv[1] (GV for lf0) */
	if (num_interp == num_ms_gvl) {
		if (num_ms_gvl == num_ts_gvl)
			HTS_Engine_load_gv_from_fn(&engine, fn_ms_gvl, fn_ts_gvl, 1,
					num_interp);
		else
			HTS_Engine_load_gv_from_fn(&engine, fn_ms_gvl, NULL, 1, num_interp);
	}
	/* load gv[2] (GV for strengths) */
	if (num_interp == num_ms_gvs) {
		if (num_ms_gvs == num_ts_gvs)
			HTS_Engine_load_gv_from_fn(&engine, fn_ms_gvs, fn_ts_gvs, 2,
					num_interp);
		else
			HTS_Engine_load_gv_from_fn(&engine, fn_ms_gvs, NULL, 2, num_interp);
	}
	/* load GV switch */
	if (fn_gv_switch != NULL)
		HTS_Engine_load_gv_switch_from_fn(&engine, fn_gv_switch);

	/* set parameter */
	HTS_Engine_set_sampling_rate(&engine, sampling_rate);
	HTS_Engine_set_fperiod(&engine, fperiod);
	HTS_Engine_set_alpha(&engine, alpha);
	HTS_Engine_set_gamma(&engine, stage);
	HTS_Engine_set_log_gain(&engine, use_log_gain);
	HTS_Engine_set_beta(&engine, beta);
	HTS_Engine_set_audio_buff_size(&engine, audio_buff_size);
	HTS_Engine_set_msd_threshold(&engine, 1, uv_threshold); /* set voiced/unvoiced threshold for stream[1] */
	HTS_Engine_set_gv_weight(&engine, 0, gv_weight_mcp);
	HTS_Engine_set_gv_weight(&engine, 1, gv_weight_lf0);
	HTS_Engine_set_gv_weight(&engine, 2, gv_weight_str);
	for (i = 0; i < num_interp; i++) {
		HTS_Engine_set_duration_interpolation_weight(&engine, i,
				rate_interp[i]);
		HTS_Engine_set_parameter_interpolation_weight(&engine, 0, i,
				rate_interp[i]);
		HTS_Engine_set_parameter_interpolation_weight(&engine, 1, i,
				rate_interp[i]);
		HTS_Engine_set_parameter_interpolation_weight(&engine, 2, i,
				rate_interp[i]);
	}
	if (num_interp == num_ms_gvm)
		for (i = 0; i < num_interp; i++)
			HTS_Engine_set_gv_interpolation_weight(&engine, 0, i,
					rate_interp[i]);
	if (num_interp == num_ms_gvl)
		for (i = 0; i < num_interp; i++)
			HTS_Engine_set_gv_interpolation_weight(&engine, 1, i,
					rate_interp[i]);
	if (num_interp == num_ms_gvs)
		for (i = 0; i < num_interp; i++)
			HTS_Engine_set_gv_interpolation_weight(&engine, 2, i,
					rate_interp[i]);

	return 0;

}
/**
 * Synth(char *text): This Function Synth
 * 		It take One Time Parameter.
 * 		Text to be converted into speech
 *
 * 	Returns: Its return the state or result of the function
 * @PARAMS: Text
 * @Auhtor: Amit Kumar
 * @Date  :
 *
 */

//int Synth(char *txt, double speech_speed)
int Synth(char *txt) {

	HTS_Boolean phoneme_alignment = FALSE;
	double speech_speed = 1.0;
	double half_tone = 0.0;
	double f;
	/* label list*/
	char **label_data = NULL;
	int label_size, i;
	if (txt == NULL)
		return -1;
	if (strlen(txt) > 0) {
		txt[strlen(txt) - 1] = '\0';
		label_data = Flite_HTS_Engine_L2S(txt, &label_size);
	}
	/* synthesis */
	HTS_Engine_load_label_from_string_list(&engine, label_data, label_size); /* load label file */
	if (phoneme_alignment) /* modify label */
		HTS_Label_set_frame_specified_flag(&engine.label, TRUE);
	if (speech_speed != 1.0) /* modify label */
		HTS_Label_set_speech_speed(&engine.label, speech_speed);
	HTS_Engine_create_sstream(&engine); /* parse label and determine state duration */
	if (half_tone != 0.0) { /* modify f0 */
		for (i = 0; i < HTS_SStreamSet_get_total_state(&engine.sss); i++) {
			f = HTS_SStreamSet_get_mean(&engine.sss, 1, i, 0);
			f += half_tone * log(2.0) / 12;
			if (f < log(10.0))
				f = log(10.0);
			HTS_SStreamSet_set_mean(&engine.sss, 1, i, 0, f);
		}
	}
	HTS_Engine_create_pstream(&engine); /* generate speech parameter vector sequence */
	HTS_Engine_create_gstream(&engine); /* synthesize speech */
	return 0;
}
/**
 *	generateWaveFile() :
 * 		Returns: Its return the state or result of the function
 * 	@PARAMS: path
 *  @Auhtor: Amit Kumar
 *  @Date  :
 *
 */

int generateWaveFile(char *wavFileName) {
	FILE *wavfp = NULL;
#ifndef HTS_EMBEDDED
	FILE *lf0fp = NULL, *mcpfp = NULL;
#endif                          /* !HTS_EMBEDDED */

	// OUTPUT
	wavfp = Getfp(wavFileName, "wb");// Amit Note: Check the scope OPTIMIZATION
	/* output */
	if (wavfp)
		HTS_Engine_save_riff(&engine, wavfp);
#ifndef HTS_EMBEDDED
	if (mcpfp)
		HTS_Engine_save_generated_parameter(&engine, mcpfp, 0);
	if (lf0fp)
		HTS_Engine_save_generated_parameter(&engine, lf0fp, 1);
#endif                          /* !HTS_EMBEDDED */
	/* close files */
#ifndef HTS_EMBEDDED
	if (mcpfp != NULL)
		fclose(mcpfp);
	if (lf0fp != NULL)
		fclose(lf0fp);
#endif                          /* !HTS_EMBEDDED */
	if (wavfp != NULL)
		fclose(wavfp);
	return 0;

}

/**
 *	destroy() : It will destroy the TTS_Engine
 * 		Returns: Its return the state or result of the function
 * 	@PARAMS: path
 *  @Auhtor: Amit Kumar
 *  @Date  :
 *
 */
int destroy() {

	/* free */
	HTS_Engine_refresh(&engine);

	/* free memory */
	HTS_Engine_clear(&engine);
	free(rate_interp);
	free(fn_ws_mcp);
	free(fn_ws_lf0);
	free(fn_ms_mcp);
	free(fn_ms_lf0);
	free(fn_ms_dur);
	free(fn_ts_mcp);
	free(fn_ts_lf0);
	free(fn_ts_dur);
	free(fn_ms_gvm);
	free(fn_ms_gvl);
	free(fn_ts_gvm);
	free(fn_ts_gvl);

	return 0;

}

/**
 *	Getfg() : Its opens the file and return the FD.
 * 		Returns: Its return the File pointer.
 * 	@PARAMS: name, opt
 *  @Auhtor: Amit Kumar
 *  @Date  :
 *
 */

FILE *Getfp(const char *name, const char *opt) {
	FILE *fp = fopen(name, opt);

	if (fp == NULL)
		Error(2, "Getfp: Cannot open %s.\n", name);

	return (fp);
}

/**
 *	addTwoStrinng : Its joins two strings
 * 		Returns: Its String
 * 	@PARAMS: String 1 String 2
 *  @Auhtor: Amit Kumar
 *  @Date  :
 *
 */

char* addTwoStrings(const char* str1, char* str2) {
	int m, n;
	int i, j;
	char *newString;
	m = strlen(str1);
	n = strlen(str2);
	newString = (char *) malloc(m + n); //*sizeof(char));
	for (i = 0; i < m; i++)
		newString[i] = str1[i];

	for (j = 0; j < n; j++)
		newString[i + j] = str2[j];
	newString[m + n] = '\0';
	return newString;

}

char *addTwoString(const char* str1, char* str2) {
	char *newString;
	int m = strlen(str1);
	int n = strlen(str2);
	newString = (char *) malloc(m + n);
	strcpy(newString, str1);
	strcat(newString, str2);
	return newString;
}

void prepareLanguageParameter(char *dir, int num_interp) {
	fn_ms_lf0 = (char **) calloc(num_interp, sizeof(char *));
	fn_ms_mcp = (char **) calloc(num_interp, sizeof(char *));
	fn_ms_dur = (char **) calloc(num_interp, sizeof(char *));
	fn_ms_str = (char **) calloc(num_interp, sizeof(char *));

	fn_ts_lf0 = (char **) calloc(num_interp, sizeof(char *));
	fn_ts_mcp = (char **) calloc(num_interp, sizeof(char *));
	fn_ts_dur = (char **) calloc(num_interp, sizeof(char *));
	fn_ts_str = (char **) calloc(num_interp, sizeof(char *));

	fn_ms_gvl = (char **) calloc(num_interp, sizeof(char *));
	fn_ms_gvm = (char **) calloc(num_interp, sizeof(char *));
	fn_ms_gvs = (char **) calloc(num_interp, sizeof(char *));

	fn_ts_gvl = (char **) calloc(num_interp, sizeof(char *));
	fn_ts_gvm = (char **) calloc(num_interp, sizeof(char *));
	fn_ts_gvs = (char **) calloc(num_interp, sizeof(char *));

	/* delta window handler for log f0 */
	fn_ws_lf0 = (char **) calloc(3, sizeof(char *));
	/* delta window handler for mel-cepstrum */
	fn_ws_mcp = (char **) calloc(3, sizeof(char *));
	/* delta window handler for strengths */
	fn_ws_str = (char **) calloc(3, sizeof(char *));

	fn_ts_dur[num_ts_dur++] = addTwoStrings(dir, "/t.1");
	fn_ts_lf0[num_ts_lf0++] = addTwoStrings(dir, "/p.1");
	fn_ts_mcp[num_ts_mcp++] = addTwoStrings(dir, "/s.1");
	fn_ts_str[num_ts_str++] = addTwoStrings(dir, "/b.1");

	fn_ms_dur[num_ms_dur++] = addTwoStrings(dir, "/k.1");
	fn_ms_lf0[num_ms_lf0++] = addTwoStrings(dir, "/v.1");
	fn_ms_mcp[num_ms_mcp++] = addTwoStrings(dir, "/sy.1");
	fn_ms_str[num_ms_str++] = addTwoStrings(dir, "/r.1");

	fn_ws_lf0[num_ws_lf0++] = addTwoStrings(dir, "/l.w1");
	fn_ws_lf0[num_ws_lf0++] = addTwoStrings(dir, "/l.w2");
	fn_ws_lf0[num_ws_lf0++] = addTwoStrings(dir, "/l.w3");
	fn_ws_mcp[num_ws_mcp++] = addTwoStrings(dir, "/m.w1");
	fn_ws_mcp[num_ws_mcp++] = addTwoStrings(dir, "/m.w2");
	fn_ws_mcp[num_ws_mcp++] = addTwoStrings(dir, "/m.w3");
	fn_ws_str[num_ws_str++] = addTwoStrings(dir, "/s.w1");
	fn_ws_str[num_ws_str++] = addTwoStrings(dir, "/s.w2");
	fn_ws_str[num_ws_str++] = addTwoStrings(dir, "/s.w3");

}

/*****************************************************************************************/

/* Error: output error message */
void Error(const int error, char *message, ...) {
	// Implements this function for Android.

	/* va_list arg;

	 fflush(stdout);
	 fflush(stderr);

	 if (error > 0)
	 fprintf(stderr, "\nError: ");
	 else
	 fprintf(stderr, "\nWarning: ");

	 va_start(arg, message);
	 vfprintf(stderr, message, arg);
	 va_end(arg);

	 fflush(stderr);
	 */
	if (error > 0)
		exit(error);
}
// This Function will be implemented at later stage.

/* GetNumInterp: get number of speakers for interpolation from argv */
int GetNumInterp(int argc, char **argv_search) {
	int num_interp = 1;
	while (--argc) {
		if (**++argv_search == '-') {
			if (*(*argv_search + 1) == 'i') {
				num_interp = atoi(*++argv_search);
				if (num_interp < 1) {
					num_interp = 1;
				}
				--argc;
			}
		}
	}
	return (num_interp);
}

char *addTwoStrings(const char *str1, char *str2);

int AMIT_TTS_ENGINE(char *dir, char *fname, char *wavfile) {

	return 0;
}

/*NDK PART */

/**
 *  Code Snippet requried IN NDK
 *  const char *j_dir = (*env)->GetStringUTFChars(env, dir, 0);
 *  after work done
 *  (*env)->ReleaseStringUTFChars(env, dir, j_dir);
 *
 * to return jstring
 * 	return (*env)->NewStringUTF(env,"ERROR");
 *
 *
 *
 * */

/**********************************************************************************************************************************************/
/*	JNI-NDK Wrapper Function																												  */
/**********************************************************************************************************************************************/

JNIEXPORT jint JNICALL
Java_com_aksharspeech_ttsengine_TTSEngine_INIT(JNIEnv env, jobject thiz) {
	//TODO : call native Init function.

	if (engine == NULL) {
		if (Init("/sdcard/rms"))

		{
			return 0;
		} else
			return -1;
	} else
		return 0;
}

JNIEXPORT jint JNICALL
Java_com_aksharspeech_ttsengine_TTSEngine_TextToSpeech(JNIEnv env, jobject thiz,
		jstring textData, jstring wavFileName) {
	//TODO : call native SYNTH function.
	/**
	 * Check if the engine started or not if not the start it ...
	 * */

	if (engine == NULL) {
		if (!Init("/sdcard/rms")) {
			return -1;

		}
	}
	if (engine != NULL) {
		const char *text;
		text = (*env)->GetStringUTFChars(env, textData, 0);

		if (Synth(text)) {
			char *wavfile;
			wavfile = (*env)->GetStringUTFChars(env, wavFileName, 0);
			generateWaveFile(wavfile);
			(*env)->ReleaseStringUTFChars(env, wavFileName, wavfile);
		}
		(*env)->ReleaseStringUTFChars(env, textData, text);

	}
	return 0;
}

JNIEXPORT void JNICALL
Java_com_aksharspeech_ttsengine_TTSEngine_Shutdown(JNIEnv env, jobject thiz) {

	destroy();
	//TODO: need to memory handling and error handling at this stage or native stage.
}

